<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>effect</title>
    <!-- <script src="../node_modules/vue/dist/vue.global.js"></script> -->
  </head>
  <body>
    <div id="app"></div>

    <script>
      const isObject = (target) => target && typeof target === 'object'
      const ReactiveFlags = { IS_REACTIVE: '__v_isReactive' }
      const proxyMap = new WeakMap()
      const TrackOpTypes = { GET: 'GET', SET: 'SET' }
      const targetMap = new WeakMap()
      // 表示当前激活的effect
      let activeEffect = null

      const trackEffect = (dep) => {
        const shouldTrack = !dep.has(activeEffect)
        if (shouldTrack) {
          // 双向记忆方便清除
          dep.add(activeEffect)
          activeEffect.deps.push(dep)
        }
      }

      /**
       * @desc 实现依赖收集
       */
      const track = (target, type, key) => {
        if (!activeEffect) return

        let depsMap = targetMap.get(target)
        if (!depsMap) {
          targetMap.set(target, (depsMap = new Map()))
        }

        let dep = depsMap.get(key)
        if (!dep) {
          depsMap.set(key, (dep = new Set()))
        }

        trackEffect(dep)
      }

      const triggerEffect = (deps) => {
        const effects = new Set(deps)
        for (const effect of effects) {
          if (effect !== activeEffect) {
            if (effect.scheduler) {
              effect.scheduler()
            } else {
              effect.run()
            }
          }
        }
      }

      /**
       * @desc 实现依赖触发
       */
      const trigger = (target, type, key, value) => {
        const depsMap = targetMap.get(target)
        if (!depsMap) return

        const deps = depsMap.get(key)
        if (!deps) return

        triggerEffect(deps)
      }

      class ReactiveEffect {
        constructor(fn, scheduler) {
          this.fn = fn
          this.active = true
          this.parent = null
          this.deps = []
          this.scheduler = scheduler
        }

        run() {
          if (!this.active) return this.fn()

          try {
            // 将激活的effect 给父类
            this.parent = activeEffect
            activeEffect = this

            // 每次收集依赖之前 清空依赖
            cleanEffect(this)
            return this.fn()
          } finally {
            activeEffect = this.parent
          }
        }
      }

      /**
       * @desc 清除依赖
       */
      const cleanEffect = (effect) => {
        const { deps } = effect
        if (deps.length <= 0) return

        for (let i = 0; i < deps.length; i++) {
          deps[i].delete(effect)
        }
        deps.length = 0
      }

      /**
       * @desc 实现effect
       */
      const effectImpl = (fn, options = {}) => {
        const _effect = new ReactiveEffect(fn, options.scheduler)

        // effect 函数初期执行一次
        _effect.run()
        const runner = _effect.run.bind(_effect)
        runner.effect = _effect
        return runner
      }

      /**
       * @desc 实现reactive函数
       */
      const reactiveImpl = (target) => {
        if (!isObject(target)) {
          console.warn(`${target} need is object`)
          return target
        }

        const existProxy = proxyMap.get(target)
        if (existProxy) return existProxy

        if (target[ReactiveFlags.IS_REACTIVE]) return target

        const proxy = new Proxy(target, {
          get: (target, key, receiver) => {
            const res = Reflect.get(target, key, receiver)

            // 依赖收集
            track(target, TrackOpTypes.GET, key)

            return isObject(res) ? reactive(res) : res
          },
          set: (target, key, newValue, receiver) => {
            const res = Reflect.set(target, key, newValue, receiver)

            // 触发依赖
            trigger(target, TrackOpTypes.set, key, newValue)

            return res
          }
        })
        proxyMap.set(target, proxy)
        return proxy
      }

      const Vue = { reactive: reactiveImpl, effect: effectImpl }

      // ---------------------- 运行部分 --------------------------------------
      const { reactive, effect } = Vue

      const data = { name: 'lixx', age: 20 }
      const state = reactive(data)

      const runner = effect(
        () => {
          document.getElementById('app').innerHTML = `我叫${state.name}, 我今天${state.age}岁了`
        },
        {
          scheduler() {
            runner()
            console.log('run')
          }
        }
      )

      setTimeout(() => {
        state.age++
      }, 5000)
    </script>
  </body>
</html>
